﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using System.Globalization;
using Medianamik.Core.Configuration;
using SubSonic.Utilities;

namespace Medianamik.Core.Sugar
{
    public static class StringHelper
    {
        private static readonly MedianamikConfigSection Config =
            (MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK);

        public static string GetStringOrNull(object item)
        {
            if (item == null || item == DBNull.Value)
                return null;

            return item.ToString();
        }

        //http://geekswithblogs.net/jawad/archive/2005/05/20/GuidVerifier.aspx
        private static Regex isGuid = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
        public static bool IsGuid(string candidate, out Guid output)
        {
            bool isValid = false;

            output = Guid.Empty;

            if (candidate != null)
            {
                if (isGuid.IsMatch(candidate))
                {
                    output = new Guid(candidate);
                    isValid = true;
                }
            }

            return isValid;
        }
        public static bool IsGuid(string candidate, ref Guid? output)
        {
            bool isValid = false;

            if (candidate != null)
            {
                if (isGuid.IsMatch(candidate))
                {
                    output = new Guid(candidate);
                    isValid = true;
                }
            }

            return isValid;
        }

        public static string PrefixIfNotEmpty(this string value, string prefix)
        {
            var result = "";

            return value.IsNullOrEmptyTrim(ref result) ? "" : prefix + result;
        }

        public static string IfNotEmptyPrefix(this string prefix, string value, string padding)
        {
            var finalPrefix = "";

            return value.IsNullOrEmptyTrim(ref finalPrefix) ? value : finalPrefix + value;
        }

        public static string SuffixIfNotEmpty(this string value, string suffix)
        {
            var result = "";

            return value.IsNullOrEmptyTrim(ref result) ? "" : result + suffix;
        }

        public static bool IsNullOrEmptyTrim(this string source)
        {
            if (source == null) return true;
            return source.Trim().Length == 0;
        }

        public static bool IsNullOrEmptyTrim(this string source, ref string result)
        {
            if (source == null) return true;
            result = source.Trim();
            return result.Length == 0;
        }

        public static string ParseCarriageReturnToHtml(this string source)
        {
            return source.Replace("\r\n", "<br />");
        }

        public static bool ContainsCaseInsensitive(this string source, string value)
        {
            return source.ToLower().Contains(value.ToLower());
        }

        public static bool ContainsWord(this string source, string value)
        {
            string clean = Regex.Replace(source, @"[^a-zA-Z0-9]", string.Empty);
            return clean.ToLower().Contains(value.ToLower());
        }

        public static bool IsGuid(this string source)
        {
            if (source != null && source.Length == 36)
            {
                Regex guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");

                return guidRegEx.IsMatch(source);
            }
            return false;
        }

        public static bool IsGuidInline(this string candidate, out Guid output)
        {
            return IsGuid(candidate, out output);
        }

        public static bool IsGuidInline(this string candidate, ref Guid? output)
        {
            return IsGuid(candidate, ref output);
        }

        public static bool IsEmail(this string source)
        {
            Regex emailRegex = new Regex(@"^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$");
            return emailRegex.IsMatch(source);
        }

        public static string Excerpt(this string source, int maximumCharacters)
        {
            if (!string.IsNullOrEmpty(source))
            {
                if (maximumCharacters < source.Length)
                {
                    var truncated = source.Substring(0, maximumCharacters);
                    return truncated.Substring(0, truncated.LastIndexOf(' '));
                }
            }
            return source;
        }

        public static string StripHtml(this string source)
        {
            return (!string.IsNullOrEmpty(source) ?
                Regex.Replace(source, @"<(.|\n)*?>", string.Empty) : string.Empty);
        }

        public static bool IsCulture(string cultureName, ref CultureInfo culture)
        {
            bool isCulture = false;
            culture = null;

            try
            {
                culture = CultureInfo.GetCultureInfo(cultureName);
                isCulture = true;
            }
            catch
            {

            }

            return isCulture;
        }

        public static string Truncate(this string source, int length)
        {
            if (source.Length > length)
            {
                source = source.Substring(0, length);
            }

            return source;
        }

        public static string Truncate(this string source, int length, string suffix)
        {
            if (source.Length > length)
            {
                source = source.Substring(0, length) + suffix;
            }

            return source;
        }

        //http://www.codeproject.com/KB/cs/ToStringExtension.aspx
        public static string ToString<T>(this IEnumerable<T> source, string separator)
        {
            // check null, note source can be passed in as null.
            if (source == null)
                throw new ArgumentException("Parameter source can not be null.");
            // check if the separator is valid
            if (string.IsNullOrEmpty(separator))
                throw new ArgumentException("Parameter separator can not be null or empty.");
            // constructs an array of string by calling ToString on each element.
            string[] array = (from s in source where s != null select s.ToString()).ToArray();
            // utilize the exsiting string.Join
            return string.Join(separator, array);
        }

        //Pourrait ajouter first et last - genre de repeater
        /// <summary>
        /// Important de mettre null aux paramètres qui n'entrent pas en vigueur dans votre cas...
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="prefix"></param>
        /// <param name="suffix"></param>
        /// <param name="enclosingStart"></param>
        /// <param name="enclosingEnd"></param>
        /// <param name="ignoreEmpty"></param>
        /// <param name="nullAsEmpty"></param>
        /// <param name="firstPrefix"></param>
        /// <param name="lastPrefix"></param>
        /// <param name="firstSuffix"></param>
        /// <param name="lastSuffix"></param>
        /// <returns></returns>
        public static string ToString<T>(this IEnumerable<T> source, string prefix,
            string suffix, string enclosingStart, string enclosingEnd, bool ignoreEmpty,
            bool nullAsEmpty, string firstPrefix, string lastPrefix, string firstSuffix,
            string lastSuffix)
        {
            IList<string> strings;

            if (!nullAsEmpty || ignoreEmpty)
                strings = source.Where(s => s != null).Select(s => s.ToString()).ToList();
            else
                strings = source.Select(s => s == null ? "" : s.ToString()).ToList();

            if (strings.Count == 0)
                return "";

            if (strings.Count == 1)
                return String.Format("{0}{1}{2}{3}{4}", enclosingStart ?? "",
                    firstPrefix ?? lastPrefix ?? prefix ?? "", strings.ElementAt(0),
                    firstSuffix ?? lastSuffix ?? suffix ?? "", enclosingEnd ?? "");

            var builder = new StringBuilder(enclosingStart ?? "");

            for (var i = 0; i < strings.Count; i++)
            {
                if (i == 0)
                    builder.AppendFormat("{0}{1}{2}", firstPrefix ?? prefix ?? "", strings.ElementAt(i),
                                         firstSuffix ?? suffix ?? "");
                else if (i == strings.Count - 1)
                    builder.AppendFormat("{0}{1}{2}", lastPrefix ?? prefix ?? "", strings.ElementAt(i),
                                         lastSuffix ?? suffix ?? "");
                else
                    builder.AppendFormat("{0}{1}{2}", prefix, strings.ElementAt(i), suffix);
            }

            return builder.Append(enclosingEnd ?? "").ToString();
        }

        public static string SanitizeXmlString(this string xml)
        {
            if (xml == null)
                return null;

            var buffer = new StringBuilder(xml.Length);

            foreach (var c in xml.Where(c => IsLegalXmlChar(c)))
            {
                buffer.Append(c);
            }

            return buffer.ToString();
        }

        /// <summary>
        /// Get whether an integer represents a legal XML 1.0 or 1.1 character. See
        /// the specification at w3.org for these characters.
        /// </summary>
        /// <param name="xmlVersion">
        /// The version number as a string. Use "1.0" for XML 1.0 character
        /// validation, and use "1.1" for XML 1.1 character validation.
        /// </param>
        public static bool IsLegalXmlChar(this int character, string xmlVersion)
        {
            switch (xmlVersion)
            {
                case "1.1": // http://www.w3.org/TR/xml11/#charsets
                    {
                        return
                        !(
                             character <= 0x8 ||
                             character == 0xB ||
                             character == 0xC ||
                            (character >= 0xE && character <= 0x1F) ||
                            (character >= 0x7F && character <= 0x84) ||
                            (character >= 0x86 && character <= 0x9F) ||
                             character > 0x10FFFF
                        );
                    }
                case "1.0": // http://www.w3.org/TR/REC-xml/#charsets
                    {
                        return
                        (
                             character == 0x9 /* == '\t' == 9   */          ||
                             character == 0xA /* == '\n' == 10  */          ||
                             character == 0xD /* == '\r' == 13  */          ||
                            (character >= 0x20 && character <= 0xD7FF) ||
                            (character >= 0xE000 && character <= 0xFFFD) ||
                            (character >= 0x10000 && character <= 0x10FFFF)
                        );
                    }
                default:
                    {
                        throw new ArgumentOutOfRangeException
                            ("xmlVersion", string.Format("'{0}' is not a valid XML version."));
                    }
            }
        }

        /// <summary>
        /// Get whether an integer represents a legal XML 1.0 character. See the  
        /// specification at w3.org for these characters.
        /// </summary>
        public static bool IsLegalXmlChar(this int character)
        {
            return character.IsLegalXmlChar("1.0");
        }

        public static string AddParam(this string queryString, string paramName, object value)
        {
            queryString = queryString.RemoveParam(paramName);

            if (queryString.Contains('?'))
                return string.Concat(queryString, "&", paramName, "=", value);

            return string.Concat(queryString, "?", paramName, "=", value);
        }

        public static string RemoveParam(this string queryString, string paramName)
        {
            var indexOfInterrogation = queryString.IndexOf('?');

            if (indexOfInterrogation > -1)
            {
                var indexOfParam = queryString.IndexOf(paramName + "=", indexOfInterrogation, StringComparison.OrdinalIgnoreCase);

                if (indexOfParam > 0) //0 devrait être ? au pire...
                {
                    var index = indexOfParam - 1;
                    var previousChar = queryString[index];
                    var nextParamIndex = queryString.IndexOf('&', indexOfParam);
                    var hasMoreParams = false;

                    if (nextParamIndex == -1)
                        nextParamIndex = queryString.Length + 1;
                    else
                    {
                        hasMoreParams = true;
                        nextParamIndex++;
                    }

                    if (previousChar == '?' && hasMoreParams)
                    {
                        queryString = queryString.Remove(indexOfParam, nextParamIndex - indexOfParam);
                    }
                    else
                    {
                        queryString = queryString.Remove(index, nextParamIndex - indexOfParam);
                    }
                }
            }

            return queryString;
        }

        public static string EncodeBase64(this string text)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
        }

        public static string UrlEncodeBase64(this string text)
        {
            return HttpUtility.UrlEncode(text.EncodeBase64());
        }

        public static string DecodeBase64(this string text)
        {
            return new UTF8Encoding().GetString(Convert.FromBase64String(text));
        }

        public static string UrlDecodeBase64(this string text)
        {
            return HttpUtility.UrlDecode(text.DecodeBase64());
        }

        public static string UrlEncode(this string source)
        {
            return HttpContext.Current.Server.UrlEncode(source);
        }

        public static string UrlDecode(this string source)
        {
            return HttpContext.Current.Server.UrlDecode(source);
        }

        public static DateTime? ToEndingComparedDate(this DateTime? date)
        {
            return date.HasValue ? (DateTime?)date.Value.ToEndingComparedDate() : null;
        }
        public static DateTime ToEndingComparedDate(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, date.Day).AddDays(1).AddSeconds(-1);
        }

        public static DateTime? ToStartingComparedDate(this DateTime? date)
        {
            return date.HasValue ? (DateTime?)date.Value.ToStartingComparedDate() : null;
        }
        public static DateTime ToStartingComparedDate(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, date.Day).AddSeconds(-1);
        }

        public static string ReplaceFirst(this string text, string search, string replace)
        {
            var pos = text.IndexOf(search, StringComparison.OrdinalIgnoreCase);

            return pos < 0 ? text : text.Substring(0, pos) + replace
                + text.Substring(pos + search.Length);
        }

        public static string ReplaceIfFirst(this string value, string stringToReplace, string replacement)
        {
            return value.StartsWith(stringToReplace, StringComparison.OrdinalIgnoreCase) ? value.ReplaceFirst(stringToReplace, replacement) : value;
        }

        public static string Translate(this string resourceKey)
        {
            return (String)HttpContext.GetGlobalResourceObject(Config.DefaultResourceSet, resourceKey);
        }

        public static string Translate(this string resourceKey, string resourceSet)
        {
            return (String)HttpContext.GetGlobalResourceObject(resourceSet, resourceKey);
        }

        public static string Serialize<T>(this T value)
        {

            if (value == null)
            {
                return null;
            }

            XmlSerializer serializer = new XmlSerializer(typeof(T));

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new UnicodeEncoding(false, false); // no BOM in a .NET string
            settings.Indent = false;
            settings.OmitXmlDeclaration = true;

            using (StringWriter textWriter = new StringWriter())
            {
                using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
                {
                    serializer.Serialize(xmlWriter, value);
                }
                return textWriter.ToString();
            }
        }

        public static T Deserialize<T>(this string xml)
        {

            if (string.IsNullOrEmpty(xml))
            {
                return default(T);
            }

            XmlSerializer serializer = new XmlSerializer(typeof(T));

            XmlReaderSettings settings = new XmlReaderSettings();
            // No settings need modifying here

            using (StringReader textReader = new StringReader(xml))
            {
                using (XmlReader xmlReader = XmlReader.Create(textReader, settings))
                {
                    return (T)serializer.Deserialize(xmlReader);
                }
            }
        }

        public static string InsertTokens(this string content, Hashtable tokens)
        {
            if (content != null)
            {
                foreach (DictionaryEntry token in tokens)
                    content = content.Replace("##" + token.Key.ToString().ToUpper() + "##", token.Value.ToString(), StringComparison.OrdinalIgnoreCase);
            }

            return content;
        }

        //Taken from http://geekswithblogs.net/michelotti/archive/2008/10/05/tryparse-extension-methods.aspx
        //http://geekswithblogs.net/michelotti/archive/2006/01/16/66015.aspx
        #region Numerical Operations

        public static int TryParseInt32(this string value)
        {
            return TryParse<int>(value, int.TryParse);
        }

        public static Int16 TryParseInt16(this string value)
        {
            return TryParse<Int16>(value, Int16.TryParse);
        }

        public static Int64 TryParseInt64(this string value)
        {
            return TryParse<Int64>(value, Int64.TryParse);
        }

        public static byte TryParseByte(this string value)
        {
            return TryParse<byte>(value, byte.TryParse);
        }

        public static bool TryParseBoolean(this string value)
        {
            return TryParse<bool>(value, bool.TryParse);
        }

        public static Single TryParseSingle(this string value)
        {
            return TryParse<Single>(value, Single.TryParse);
        }

        public static Double TryParseDoube(this string value)
        {
            return TryParse<Double>(value, Double.TryParse);
        }

        public static Decimal TryParseDecimal(this string value)
        {
            return TryParse<Decimal>(value, Decimal.TryParse);
        }

        public static DateTime TryParseDateTime(this string value)
        {
            return TryParse<DateTime>(value, DateTime.TryParse);
        }

        #region Nullable

        public static int? TryParseInt32Nullable(this string value)
        {
            return TryParseNullable<int>(value, int.TryParse);
        }

        public static Int16? TryParseInt16Nullable(this string value)
        {
            return TryParseNullable<Int16>(value, Int16.TryParse);
        }

        public static Int64? TryParseInt64Nullable(this string value)
        {
            return TryParseNullable<Int64>(value, Int64.TryParse);
        }

        public static byte? TryParseByteNullable(this string value)
        {
            return TryParseNullable<byte>(value, byte.TryParse);
        }

        public static bool? TryParseBooleanNullable(this string value)
        {
            return TryParseNullable<bool>(value, bool.TryParse);
        }

        public static Single? TryParseSingleNullable(this string value)
        {
            return TryParseNullable<Single>(value, Single.TryParse);
        }

        public static Double? TryParseDoubeNullable(this string value)
        {
            return TryParseNullable<Double>(value, Double.TryParse);
        }

        public static Decimal? TryParseDecimalNullable(this string value)
        {
            return TryParseNullable<Decimal>(value, Decimal.TryParse);
        }

        public static DateTime? TryParseDateTimeNullable(this string value)
        {
            return TryParseNullable<DateTime>(value, DateTime.TryParse);
        }

        #endregion

        #region Private Members

        public delegate bool TryParseDelegate<T>(string s, out T result);

        private static T TryParse<T>(this string value, TryParseDelegate<T> parse) where T : struct
        {
            T result;
            parse(value, out result);

            return result;
        }

        public static T? TryParseNullable<T>(string value, TryParseDelegate<T> tryParse)
            where T : struct
        {
            var result = default(T?);

            if (!string.IsNullOrEmpty(value))
            {
                T temp;
                if (tryParse(value, out temp))
                    result = temp;
            }

            return result;
        }

        #endregion

        #endregion

        public static string PhysicalPathToRelativePath(this HttpServerUtility server, string physicalPath)
        {
            return physicalPath.Replace(server.MapPath("/"), "/").Replace(@"\", "/");
        }


        public static string ToStringWithDetails(this Exception ex)
        {
            var sb = new StringBuilder(ex.Message);

            while (ex.InnerException != null)
            {
                sb.AppendLine(ex.Message);
                ex = ex.InnerException;
            }

            return sb.ToString();
        }

        public static string StripAfter(this string s, string marker, bool dismissMarker)
        {
            if (s == null)
                return null;

            var index = s.IndexOf(marker);

            return index >= 0 ? s.Substring(0, (index - (dismissMarker ? 0 : -marker.Length))) : s;
        }

        public static string StripBefore(this string s, string marker, bool dismissMarker)
        {
            if (s == null)
                return null;

            var index = s.IndexOf(marker);

            return index >= 0 ? s.Substring((index - (dismissMarker ? -marker.Length : 0))) : s;
        }
    }

    /// <summary>
    /// A StreamReader that excludes XML-illegal characters while reading.
    /// http://seattlesoftware.wordpress.com/2008/09/11/hexadecimal-value-0-is-an-invalid-character/
    /// </summary>
    public class XmlSanitizingStream : StreamReader
    {
        /// <summary>
        /// The charactet that denotes the end of a file has been reached.
        /// </summary>
        private const int EOF = -1;

        /// <summary>Create an instance of XmlSanitizingStream.</summary>
        /// <param name="streamToSanitize">
        /// The stream to sanitize of illegal XML characters.
        /// </param>
        public XmlSanitizingStream(Stream streamToSanitize)
            : base(streamToSanitize, true)
        { }

        public override int Read()
        {
            // Read each character, skipping over characters that XML has prohibited

            int nextCharacter;

            do
            {
                // Read a character

                if ((nextCharacter = base.Read()) == EOF)
                {
                    // If the character denotes the end of the file, stop reading

                    break;
                }
            }

            // Skip the character if it's prohibited, and try the next

            while (!nextCharacter.IsLegalXmlChar());

            return nextCharacter;
        }



        public override int Peek()
        {
            // Return the next legl XML character without reading it 

            int nextCharacter;

            do
            {
                // See what the next character is 

                nextCharacter = base.Peek();
            }
            while
            (
                // If it's prohibited XML, skip over the character in the stream
                // and try the next.

                !nextCharacter.IsLegalXmlChar() &&
                (nextCharacter = base.Read()) != EOF
            );

            return nextCharacter;

        } // method

        #region Read*() method overrides

        // The following methods are exact copies of the methods in TextReader, 
        // extracting by disassembling it in Refelctor

        public override int Read(char[] buffer, int index, int count)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException("count");
            }
            if ((buffer.Length - index) < count)
            {
                throw new ArgumentException();
            }
            int num = 0;
            do
            {
                int num2 = this.Read();
                if (num2 == -1)
                {
                    return num;
                }
                buffer[index + num++] = (char)num2;
            }
            while (num < count);
            return num;
        }

        public override int ReadBlock(char[] buffer, int index, int count)
        {
            int num;
            int num2 = 0;
            do
            {
                num2 += num = this.Read(buffer, index + num2, count - num2);
            }
            while ((num > 0) && (num2 < count));
            return num2;
        }

        public override string ReadLine()
        {
            StringBuilder builder = new StringBuilder();
            while (true)
            {
                int num = this.Read();
                switch (num)
                {
                    case -1:
                        if (builder.Length > 0)
                        {
                            return builder.ToString();
                        }
                        return null;

                    case 13:
                    case 10:
                        if ((num == 13) && (this.Peek() == 10))
                        {
                            this.Read();
                        }
                        return builder.ToString();
                }
                builder.Append((char)num);
            }
        }

        public override string ReadToEnd()
        {
            int num;
            char[] buffer = new char[0x1000];
            StringBuilder builder = new StringBuilder(0x1000);
            while ((num = this.Read(buffer, 0, buffer.Length)) != 0)
            {
                builder.Append(buffer, 0, num);
            }
            return builder.ToString();
        }

        #endregion

    }
}
